.com
Hosted by:
Unit testing expertise at your fingertips!
Home | Discuss | Lists

Transaction Rollback Teardown

The book has now been published and the content of this chapter has likely changed substanstially.
Please see page 668 of xUnit Test Patterns for the latest information.
How do we tear down the Test Fixture when it is in a relational database?

We rollback the uncommitted test transaction as part of the teardown.

Sketch Transaction Rollback Teardown embedded from Transaction Rollback Teardown.gif

A large part of making tests repeatable and robust is ensuring that the test fixture is torn down after each test. Leftover objects and database records, open files and connections can at best cause performance degradations and at worst cause tests to fail or systems to crash. While some of these resources may be cleaned up automatically by garbage collection, others may be left hanging if they are not torn down explicitly.

Writing tearDown code that can be relied upon to clean up properly in all possible circumstances is challenging and time-consuming. It involves understanding what could be left over for each possible outcome of the test and writing code to deal with it. This Complex Teardown (see Obscure Test on page X) introduces a fair bit of Conditional Test Logic (page X) and worst of all Untestable Test Code (see Hard to Test Code on page X).

We can avoid making any lasting changes to the database contents by not committing the transaction and taking advantage of the roll back capabilities of the database.

How It Works

Our test starts a new test transaction, sets up the fixture, exercises the system under test (SUT) and verifies the outcome of the test. Each of these steps may involve interacting with the database. At the end of the test, it rolls back the test transaction. This prevents any of the changes from becoming persistent.

When To Use It

We can use Transaction Rollback Teardown when we are using a Fresh Fixture (page X) approach with a SUT that includes a database that supports rolling back a transaction. There are, however, a couple of prerequisites for using Transaction Rollback Teardown:

The SUT must expose methods that are normally called in the context of an existing transaction by a Humble Transaction Controller (see Humble Object on page X). That is they should not start their own transaction and must never commit a transaction. If we are doing test-driven development, this design will come about as a result of applying the Transaction Rollback Teardown pattern as we write our code. If we are retrofitting the tests to software that is already written, we may need to refactor the code to use a Humble Transaction Controller before we can use Transaction Rollback Teardown.

The nice thing about Transaction Rollback Teardown is that it leaves the database in exactly the same state as when we started the test regardless of what changes we made to the database contents. As a result, we do not need to do any reasoning about what needs to be cleaned up and what does not. Changes to the database schema or what was already in the database do not affect our tearDown logic. Clearly, this is much simpler to apply than Table Truncation Teardown (page X).

The usual caveats apply to any tests that run against a real database; the tests will take approximately fifty (yes 50!) times as long to run. This will almost surely resulting Slow Tests (page X) unless we replace the real database with an In-Memory Database (see Fake Object on page X). Since we are depending on the transactional properties of the database, a simple Fake Database (see Fake Object) will probably not be sufficient unless it supports ACID.

Another issue with Transaction Rollback Teardown is that we have to be sure that we are not doing anything that results in a commit anywhere in the tests or the code they exercise. See the the sidebar Transaction Rollback Pain (page X)
Include the sidebar 'Transaction Rollback Pain' on opposite page.
for some examples of where commits can sneak in and cause havoc.

Implementation Notes

A few members of the xUnit family support Transaction Rollback Teardown directly and open-source extensions may be available for other members. If nothing is available, coding it ourselves is not very complicated. The more significant implementation consideration is giving the test(s) access to non-transactional methods on the SUT. Most domain models objects are non-transactional so this should not be a problem for unit tests of domain objects. We are more likely to have a problem when we are writing Subcutaneous Tests (see Layer Test on page X) against a Service Facade[CJ2EEP] since these methods often do transaction control. If this is the case, we will need to expose a non-transactional version of them by refactoring to the Humble Transaction Controller pattern. We could either use a transactional Decorator[GOF] as a separate object or simply have the transactional methods delegate to the non-transaction versions of the methods on self. This is called a Poor Man's Humble Object (see Humble Object).

If the methods to be tested exist but are not visible to client, we will need to expose them to the test. We can do this either by making them public or we can expose them indirectly by using a Test-Specific Subclass (page X). We could also do an Extract Testable Component (page X) refactoring to move the non-transactional versions of the methods to a different class and make them visible to the test from there.

Any reading of the updated data in the database must be done within the context of the same transaction. This normally is not a problem except when we are trying to simulate or test concurrency. If we are using an object-relational mapping (ORM) layer such as Toplink, (n)Hibernate or EJB 3.0, we may need to force the ORM to write the changes made to the objects to the database so that methods that read the database directly (from within the same transactional context) can see them. For example, EJB 3.0 provides the EntityManager.flush static method to do this.

Motivating Example

Here is an example of a test that is attempting to use Guaranteed Inline Teardown (see Inline Teardown on page X) to remove all the records it created.

   public void testGetFlightsByOriginAirport_NoInboundFlights() throws Exception {
      // Fixture setup
      BigDecimal outboundAirport = createTestAirport("1OF");
      BigDecimal inboundAirport = createTestAirport("1IF");
      FlightDto expFlightDto = null;
      try {     
         expFlightDto = createTestFlight(outboundAirport, inboundAirport);
         // Exercise System
         List flightsAtDestination1 = facade.getFlightsByOriginAirport( inboundAirport);
         // Verify Outcome
         assertEquals( 0, flightsAtDestination1.size() );
      } finally {
         facade.removeFlight( expFlightDto.getFlightNumber() );
         facade.removeAirport( outboundAirport );
         facade.removeAirport( inboundAirport );
      }
   }
Example NaiveMultiResourceGuaranteedTeardown embedded from java/com/clrstream/ex6/services/test/InlineTeardownExampleTest.java

This code is neither easy to write nor is it even correct! (See Inline Teardown for an explanation of what is wrong here.) Trying to keep track of all the objects the SUT has created and then tearing them down one by one in a safe manner is very tricky.

Refactoring Notes

We can avoid most of the issues coordinating Inline Teardown of multiple resources in a safe way by using Transaction Rollback Teardown and blasting away all the changes to the objects in one fell swoop. Most of the refactoring work is to delete the existing tear down code from the finally clause and inserting a call to the 's abortTransaction method. We also need to put the call to beginTransaction before we do any fixture set up and we have to modify the Creation Methods (page X) to ensure that they do not commit a transaction either. We do this by having them call a non-transactional version of the methods on the service facade.

Example: Object Transaction Rollback Teardown

This is what the test looks like when we are done:

   public void testGetFlightsByOrigin_NoInboundFlight_TRBTD() throws Exception {
      // Fixture setup
      TransactionManager.beginTransaction();
      BigDecimal outboundAirport = createTestAirport("1OF");
      BigDecimal inboundAirport = null;
      FlightDto expectedFlightDto = null;
      try {
         inboundAirport = createTestAirport("1IF");
         expectedFlightDto = createTestFlight( outboundAirport, inboundAirport);
         // Exercise System
         List flightsAtDestination1 = facade.getFlightsByOriginAirport(inboundAirport);
         // Verify Outcome
         assertEquals(0,flightsAtDestination1.size());
      } finally {
         TransactionManager.abortTransaction();          
      }
   }
Example TransactionRollbackTeardown embedded from java/com/clrstream/ex6/services/test/InlineTeardownExampleTest.java

In this refactored test we have replaced the multiple lines of tear down code in the finally clause with a single call to abortTransaction. We still need the finally clause because this example is using Inline Teardown; we could easily move this call to the TransactionManager to the tearDown method because it is so generic.

In this example, Transaction Rollback Teardown is being used to undo the fixture setup done by the various Creation Methods we called earlier in the test. The fixture objects have not yet been committed to the database but because getFlightsFromAirport is being called within the context of the transaction, it returns the newly added but not yet committed flights. (That is the "C" for "Consistent" in ACID working on our behalf!)

   private BigDecimal createTestAirport(String airportName) throws FlightBookingException {
      BigDecimal newAirportId = facade._createAirport( airportName, " Airport" + airportName,
                                   "City" + airportName);
      return newAirportId;
   }
Example NonTxCreationMethod embedded from java/com/clrstream/ex6/services/test/InlineTeardownExampleTest.java

The creation method calls the non-transactional version of the facade method (an example of a Poor Man's Humble Object.)

   public BigDecimal createAirport( String airportCode, String name,
                                    String nearbyCity)
            throws FlightBookingException{
      TransactionManager.beginTransaction();
      BigDecimal airportId = _createAirport(airportCode, name, nearbyCity);
      TransactionManager.commitTransaction();
      return airportId;
   }
  
   // private, non-transactional version for use by tests:
   BigDecimal _createAirport( String airportCode, String name,
                              String nearbyCity)
            throws DataException, InvalidArgumentException {
      Airport airport = dataAccess.createAirport(airportCode,name,nearbyCity);
      logMessage("CreateFlight", airport.getCode());
      return airport.getId();
   }
Example PoorMansHumbleTransactionController embedded from java/com/clrstream/ex6/services/test/NonTxFlightMngtFacade.java

If the method we were exercising (e.g. getFlightsFromAirport) did modify the state of the SUT and it did begin and end its own transaction, we would have to do a similar refactoring on it as well.

Example: Database Transaction Rollback Teardown

The first example hid the database from the code behind a data access layer that returned or accepted objects. This is common when using the Domain Model[PEAA] pattern for organizing the business logic. Transaction Rollback Teardown is most commonly used when manipulating the database directly in our application logic (a style known as a Transaction Script[PEAA].) The following example illustrates this using .Net row sets (or something similar.)

   [TestFixture]
   public class TransactionRollbackTearDownTest
   {
      private SqlConnection _Connection;
      private SqlTransaction _Transaction;

      public TransactionRollbackTearDownTest()
      {
      }

      [SetUp]
      public void Setup()
      {        
         string dbConnectionString  = ConfigurationSettings.
                           AppSettings.Get("DbConnectionString");
         _Connection = new SqlConnection(dbConnectionString);
         _Connection.Open();
         _Transaction = _Connection.BeginTransaction();
      }

      [TearDown]
      public void TearDown()
      {
         _Transaction.Rollback();
         _Connection.Close();
         // Avoid NUnit "instance behavior" bug:
         _Transaction = null;
         _Connection = null;
      }

      [Test]
      public void AnNUnitTest()
      {
         const string C_INSERT_SQL = "INSERT INTO Invoice(Amount, Tax, CustomerId)" +
            " VALUES({0}, {1}, {2})";
         SqlCommand cmd = _Connection.CreateCommand();
         cmd.Transaction = _Transaction;
         cmd.CommandText = string.Format( C_INSERT_SQL,
                        new object[] {"100.00", "7.00", 2001});
         // Exercise SUT
         cmd.ExecuteNonQuery();
         // Verify result:
         //   etc.}
   }
}
Example TransactionRollbackTeardownUsingSql embedded from CSharp/TransactionRollbackTearDownTest.cs

This examples uses Implicit Setup (page X) to establish the connection and start the transaction. After the Test Method (page X) has run, it uses Implicit Teardown (page X) to rollback the transaction and close the connection. We assign null to the instance variables because NUnit does not create a separate Testcase Object (page X) for each Test Method like most other members of xUnit. See the sidebar There's Always an Exception (page X) for details.



Page generated at Wed Feb 09 16:39:34 +1100 2011

Copyright © 2003-2008 Gerard Meszaros all rights reserved

All Categories
Introductory Narratives
Web Site Instructions
Code Refactorings
Database Patterns
DfT Patterns
External Patterns
Fixture Setup Patterns
Fixture Teardown Patterns
Front Matter
Glossary
Misc
References
Result Verification Patterns
Sidebars
Terminology
Test Double Patterns
Test Organization
Test Refactorings
Test Smells
Test Strategy
Tools
Value Patterns
XUnit Basics
xUnit Members
All "Database Patterns"
Database Sandbox
Stored Procedure Test
Table Truncation Teardown
Transaction Rollback Teardown